/*
 * create by 陈云亮(shiny_vc@163.com)
 */
cmez.CMeditor = zk.$extends(zul.Widget, {
    _height: '200',
    _value: '',
    _debug: false,
    _disabled: false,
    _border: 'normal',

    _editor: null,
    _theme: 'default',
    _mode: 'xml',
    _lineNumbers: true,
    _styleActiveLine: true,
    _autoCloseTags: false,
    _foldGutter: false,
    _scrollbarStyle: 'native',
    _lineWrapping: true,
    _autocomplete: false,
    _highlightSelection: false,
    _matchTags: false,
    _showTrailingSpace: false,
    _tabSize: 4,
    _indentUnit: 2,
    _indentWithTabs: false,
    _fullScreen: false,
    _smartIndent: false,
    _type: 'normal',
    _orig: null,
    _origLeft: null,
    _collapse: false,
    _connect: null,
    _focus: false,
    _hints: null,
    _lint: false,

    $define: {
        focus: function () {
            if (this._editor)
                CodeMirror.signal(this._editor, this._focus ? 'focus' : 'blur', this._editor);
        },
        hints: function (val) {
            this._hints = val;
        },
        type: function () {
            this._sync();
        },
        origLeft: function () {
            this._sync();
        },
        orig: function () {
            this._sync();
        },
        collapse: function () {
            this._sync();
        },
        connect: function () {
            this._sync();
        },
        border: function () {
            if (this._editor) {
                if (this._border == 'normal')
                    this.$n().style.border = 1;
                else
                    this.$n().style.border = 0;
            }
        },
        mode: function () {
            if (this._editor) {
                if (this._type == 'diff') {
                    this._editor.editor().setOption("mode", this._mode);
                } else {
                    this._editor.setOption("mode", this._mode);
                }
            }
        },
        theme: function () {
            if (this._editor) {
                if (this._theme && this._theme !== 'default')
                    zk.loadCSS(zk.ajaxURI("/web/js/cmez/ext/theme/" + this._theme + ".css", {au: true}));
                if (this._type == 'diff') {
                    this._editor.editor().setOption("theme", this._theme);
                    if (this._editor.leftOriginal())
                        this._editor.leftOriginal().setOption("theme", this._theme);
                    this._editor.rightOriginal().setOption("theme", this._theme);
                } else {
                    this._editor.setOption("theme", this._theme);
                }
            }
        },
        value: function () {
            if (this._type == 'diff') {
                this.$n().innerHTML = '';
                this._editor = null;
                this._init();
            } else {
                if (this._editor) {
                    if (!this._disabled)
                        this._editor.off("changes", this._onChanges);
                    this._editor.setValue(this._value);
                    if (this._editor.lastCursorPosition)
                        this.setCursor(this._editor.lastCursorPosition);
                    if (!this._disabled)
                        this._editor.on("changes", this._onChanges);
                }
            }
        },
        disabled: function () {
            if (this._editor)
                this._editor.setOption("readOnly", this._disabled);
        },
        scrollbarStyle: function () {
            if (this._editor)
                this._editor.setOption("scrollbarStyle", this._scrollbarStyle);
        },
        lineWrapping: function () {
            if (this._editor)
                this._editor.setOption("lineWrapping", this._lineWrapping);
        },
        lineNumbers: function () {
            if (this._editor)
                this._editor.setOption("lineNumbers", this._lineNumbers);
        },
        styleActiveLine: function () {
            if (this._editor)
                this._editor.setOption("styleActiveLine", this._styleActiveLine);
        },
        autoCloseTags: function () {
            if (this._editor)
                this._editor.setOption("autoCloseTags", this._autoCloseTags);
        },
        foldGutter: function () {
            if (this._editor) {
                this._editor.setOption("foldGutter", this._foldGutter);
                if (!this._debug)
                    this._editor.setOption("gutters", ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]);
            }
        },
        lint: function () {
            if (this._editor) {
                let options = this._editor.getOption("gutters") | [];
                if (this._lint) {
                    options.push("CodeMirror-lint-markers");
                } else {
                    let pos = jq.inArray('CodeMirror-lint-markers', options);
                    if (pos >= 0)
                        options.splice(pos, 1);
                }
                this._editor.setOption("gutters", options);
            }
        },
        debug: function () {
            if (this._editor && this._mode == 'epscript') {
                if (this._debug)
                    this._editor.setOption("gutters", ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "breakpoints"]);
                else if (this._foldGutter)
                    this._editor.setOption("gutters", ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]);
                else
                    this._editor.setOption("gutters", []);
            }
        },
        autocomplete: function () {
            if (this._editor) {
                if (this._autocomplete) {
                    if (this._mode == 'eppage') {
                        this._editor.setOption("extraKeys", {
                            "'<'": this._xmlCompleteAfter,
                            "'/'": this._xmlCompleteIfAfterLt,
                            "' '": this._xmlCompleteIfInTag,
                            "'='": this._xmlCompleteIfInTag,
                            "Ctrl-/": function (cm) {
                                CodeMirror.showHint(cm, CodeMirror.hint.xml, {schemaInfo: tags});
                            }
                        });
                    } else if (this._mode == 'epscript') {
                        this._editor.setOption("extraKeys", {
                            "Ctrl-/": "autocomplete"
                        });
                    }
                } else {
                    this._editor.setOption("extraKeys", {});
                }
            }
        },
        highlightSelection: function () {
            if (this._editor) {
                if (this._type == 'diff') {
                    this._editor.setShowDifferences(this._highlightSelection);
                } else {
                    if (this._highlightSelection)
                        this._editor.setOption("highlightSelectionMatches", {showToken: /\w/});
                    else
                        this._editor.setOption("highlightSelectionMatches", {});
                }
            }
        },
        matchTags: function () {
            if (this._editor) {
                if (this._matchTags) {
                    this._editor.setOption("matchTags", {bothTags: true});
                    this._editor.setOption("extraKeys", zk.override(this._editor.options, {}, {bothTags: true}));
                } else
                    this._editor.setOption("matchTags", {bothTags: false});
            }
        },
        showTrailingSpace: function () {
            if (this._editor)
                this._editor.setOption("showTrailingSpace", this._showTrailingSpace);
        },
        tabSize: function () {
            if (this._editor) {
                this._editor.setOption("tabSize", this._tabSize);
                this._editor.refresh();
            }
        },
        indentUnit: function () {
            if (this._editor) {
                this._editor.setOption("indentUnit", this._indentUnit);
                this._editor.refresh();
            }
        },
        indentWithTabs: function () {
            if (this._editor) {
                this._editor.setOption("indentWithTabs", this._indentWithTabs);
                this._editor.refresh();
            }
        },
        fullScreen: function () {
            if (this._editor)
                this._editor.setOption("fullScreen", this._fullScreen);
        },
        smartIndent: function () {
            if (this._editor)
                this._editor.setOption("smartIndent", this._smartIndent);
        }
    },
    _sync: function () {
        if (this._editor) {
            if (this._type == 'diff')
                this.$n().innerHTML = '';
            else
                this._editor.toTextArea();
            this._editor = null;
        }
        if (this.desktop)
            this._init();
    },
    setCursor: function (data) {
        if (this._editor) {
            this._editor.lastCursorPosition = data;
            this._editor.setCursor(data);
            this._editor.scrollIntoView(null, Math.round(jq(".CodeMirror-scroll").height() / 2));
            this._editor.focus();
        }
    },
    setInsertText: function (data) {
        if (this._editor) {
            var doc = this._editor.getDoc();
            var cursor = doc.getCursor();
            if (data.line >= 0)
                cursor = {line: data.line, ch: data.ch};
            this._editor.lastCursorPosition = cursor;
            this._editor.setCursor(cursor);
            doc.replaceRange(data.text, cursor);
            this._editor.focus();
        }
    },
    refresh: function () {
        if (this._editor) {
            this._editor.refresh();
            try {
                this._editor.setCursor(this._editor.lastCursorPosition);
                this._editor.scrollIntoView(this._editor.lastCursorPosition, Math.round(jq(".CodeMirror-scroll").height() / 2));
                this._editor.focus();
            } catch (err) {
            }
        }
    },
    focus: function () {
        if (this._editor)
            this._editor.focus();
    },
    setExeCmd: function (cmd) {
        if (this._editor) {
            if (cmd == 'formatAll') {
                this._editor.execCommand('selectAll');
                this._editor.execCommand('indentAuto');
                this._editor.getDoc().setCursor(1, 0);
                this._editor.focus();
            } else if (cmd == 'format') {
                this._editor.execCommand('indentAuto');
            } else
                this._editor.execCommand(cmd);
        }
    },
    bind_: function () {
        this.$supers('bind_', arguments);
        zWatch.listen({onSize: this});
        var wgt = this;
        if (this._theme && this._theme !== 'default')
            zk.loadCSS(zk.ajaxURI("/web/js/cmez/ext/theme/" + this._theme + ".css", {au: true}));
        setTimeout(function () {
            wgt._init();
        }, 50);
    },
    unbind_: function () {
        zWatch.unlisten({onSize: this});
        if (this._editor) {
            if (this._type == 'diff')
                this.$n().innerHTML = "";
            else
                this._editor.toTextArea();
        }
        this.$supers('unbind_', arguments);
    },
    dropEffect_: function (over) {
        this.$super('dropEffect_', over);
        this._dragging = over;
    },
    doMouseMove_: function (evt) {
        if (this._matchTags && this._dragging) {
            var cm = this._editor.coordsChar({left: evt.data.pageX, top: evt.data.pageY});
            this._editor.lastCursorPosition = cm;
            this._editor.setCursor(cm);
            CodeMirror.signal(this._editor, 'toMatchingTag', this._editor);
        }
    },
    onDrop_: function (drag, evt) {
        if (this._editor) {
            var cm = this._editor.coordsChar({left: evt.data.pageX, top: evt.data.pageY});
            this._editor.lastCursorPosition = cm;
            var data = zk.copy({dragged: drag.control, line: cm.line, ch: cm.ch}, evt.data);
            this.fire('onDrop', data, null, zk.Widget.auDelay);
            this._dragging = false;
        }
    },
    _init: function () {
        if (this._type == 'diff') {
            if (this._orig) {
                var wgt = this;
                zk.loadCSS(zk.ajaxURI("/web/js/cmez/ext/addon/merge/merge-min.css", {au: true}));
                wgt._editor = CodeMirror.MergeView(wgt.$n(), {
                    value: wgt._value,
                    origLeft: wgt._origLeft,
                    orig: wgt._orig,
                    lineNumbers: wgt._lineNumbers,
                    mode: wgt._mode,
                    highlightDifferences: wgt._highlightSelection,
                    connect: wgt._connect,
                    collapseIdentical: wgt._collapse
                });
                wgt._editor.editor().setOption("theme", wgt._theme);
                if (wgt._editor.leftOriginal())
                    wgt._editor.leftOriginal().setOption("theme", wgt._theme);
                wgt._editor.rightOriginal().setOption("theme", wgt._theme);
            }
            this.$n().style.border = 0;
        } else {
            var editorOptions = {
                uuid: this.uuid,
                extraKeys: {},
                gutters: [],
                theme: this._theme,
                lineNumbers: this._lineNumbers,
                styleActiveLine: this._styleActiveLine,
                lineWrapping: this._lineWrapping,
                autoCloseTags: this._autoCloseTags,
                scrollbarStyle: this._scrollbarStyle,
                readOnly: this._disabled,
                foldGutter: this._foldGutter,
                showTrailingSpace: this._showTrailingSpace,
                tabSize: this._tabSize,
                indentUnit: this._indentUnit,
                indentWithTabs: this._indentWithTabs,
                smartIndent: this._smartIndent,
                lint: this._lint
            };
            if (this._fullScreen) {
                zk.override(editorOptions.extraKeys, {}, {
                    "F11": function (cm) {
                        cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                    },
                    "Esc": function (cm) {
                        if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
                    }
                });
            }
            if (this._matchTags && (this._mode == 'eppage' || this._mode == 'xml')) {
                editorOptions.matchTags = {bothTags: true};
                zk.override(editorOptions.extraKeys, {}, {
                    "Ctrl-J": "toMatchingTag"
                });
            }
            if (this._highlightSelection)
                editorOptions.highlightSelectionMatches = {showToken: /\w/};
            if (this._lineNumbers)
                editorOptions.gutters.push("CodeMirror-linenumbers");
            if (this._foldGutter)
                editorOptions.gutters.push("CodeMirror-foldgutter");
            if (this._debug && this._mode == 'epscript')
                editorOptions.gutters.push(["breakpoints"]);
            if (this._lint) {
                zk.loadScript(zk.ajaxURI("/web/js/cmez/ext/addon/lint/lint-min.js", {au: true}));
                if (this._mode === 'epscript' || this._mode === 'javascript') {
                    zk.loadScript(zk.ajaxURI("/web/js/cmez/ext/jshint.js", {au: true}));
                    zk.loadScript(zk.ajaxURI("/web/js/cmez/ext/addon/lint/javascript-lint-min.js", {au: true}));
                }
                if (this._mode === 'css') {
                    zk.loadScript(zk.ajaxURI("/web/js/cmez/ext/csslint.js", {au: true}));
                    zk.loadScript(zk.ajaxURI("/web/js/cmez/ext/addon/lint/css-lint-min.js", {au: true}));
                }
                if (this._mode === 'json') {
                    zk.loadScript(zk.ajaxURI("/web/js/cmez/ext/jsonlint.js", {au: true}));
                    zk.loadScript(zk.ajaxURI("/web/js/cmez/ext/addon/lint/json-lint-min.js", {au: true}));
                }
                editorOptions.gutters.push("CodeMirror-lint-markers");
            }
            if (this._mode === 'json')
                this._mode = 'application/json';
            else if (this._mode == 'javascript')
                editorOptions.mode = {name: "javascript", globalVars: false};
            else if (this._mode == 'eppage') {
                if (!this._disabled)
                    zk.loadScript(zk.ajaxURI("/web/js/cmez/ext/components-min.js", {au: true}));
                CodeMirror.defineMode("eppage", function (config) {
                    return CodeMirror.multiplexingMode(
                        CodeMirror.getMode(config, 'xml'),
                        {
                            open: /<script\s*.*>/,
                            close: /<\/script>/,
                            mode: CodeMirror.getMode(config, "javascript"),
                            delimStyle: "code-block"
                        },
                        {
                            open: /<html\s*.*>/,
                            close: /<\/html>/,
                            mode: CodeMirror.getMode(config, "text/html"),
                            delimStyle: "code-block"
                        },
                        {
                            open: /<style\s*.*>/,
                            close: /<\/style>/,
                            mode: CodeMirror.getMode(config, "css"),
                            delimStyle: "code-block"
                        },
                        {
                            open: /<zscript\s*.*>/,
                            close: /<\/zscript>/,
                            mode: CodeMirror.getMode(config, "text/x-java"),
                            delimStyle: "code-block"
                        },
                        {
                            open: /<attribute\s+.*:name="\w+">/,
                            close: /<\/attribute>/,
                            mode: CodeMirror.getMode(config, "javascript"),
                            delimStyle: "code-block"
                        },
                        {
                            open: /<attribute\s+name="\w+">/,
                            close: /<\/attribute>/,
                            mode: CodeMirror.getMode(config, "text/x-java"),
                            delimStyle: "code-block"
                        }
                    );
                });
            }
            editorOptions.mode = this._mode;
            if (!this._disabled && this._autocomplete) {
                if (this._mode == 'eppage') {
                    zk.override(editorOptions.extraKeys, {}, {
                        "'<'": this._xmlCompleteAfter,
                        "'/'": this._xmlCompleteIfAfterLt,
                        "' '": this._xmlCompleteIfInTag,
                        "'='": this._xmlCompleteIfInTag,
                        "Ctrl-/": function (cm) {
                            CodeMirror.showHint(cm, CodeMirror.hint.xml, {schemaInfo: tags});
                        }
                    });
                } else {//else
                    zk.override(editorOptions.extraKeys, {}, {
                        "Ctrl-/": "autocomplete"
                    });
                    if (this._mode == 'javascript') {
                        CodeMirror.commands.autocomplete = function (cm) {
                            CodeMirror.showHint(cm, CodeMirror.javascriptHint);
                        }
                    } else {
                        if (CodeMirror.epKeywords) {
                            delete CodeMirror.epKeywords;
                        }
                        if (this._hints) {
                            var hints = this._hints.split(";");
                            CodeMirror.epKeywords = new Array(hints.length);
                            for (var i = 0; i < hints.length; i++)
                                CodeMirror.epKeywords[i] = '$' + hints[i];
                        } else {
                            CodeMirror.epKeywords = [];
                        }
                        if (this._mode == 'epscript') {
                            $.merge(CodeMirror.epKeywords, ["debug(obj)",
                                "cache(name,value)",
                                "exists(name)",
                                "print(obj)",
                                "createVariable(name,type,scope)",
                                "setValue(name,value)",
                                "getValue(name)",
                                "get$(name)",
                                "set$(name,value)",
                                "get0$(name)",
                                "set0$(name,value)",
                                "list$(name,value)",
                                "load(sql,expr)",
                                "load(layer,expr)",
                                "load(layer,taskId,expr)",
                                "getSerial(name,len)",
                                "resetSerial(name)",
                                "deleteSerial(name)",
                                "invoke(str,args)",
                                "isEmpty(obj)",
                                "isDefine(name)",
                                "isEquals(src,target)",
                                "go(taskId,code)",
                                "getObject(name)",
                                "i18n(name)",
                                "#include",
                                "#app",
                                "#db",
                                "#dt",
                                "#util",
                                "#bpm",
                                "parent"]);
                            $.merge(CodeMirror.epKeywords, ("break case catch continue default delete do else false finally for function " +
                                "if in instanceof new null return switch throw true try typeof var void while with").split(" "));
                            zk.override(editorOptions.extraKeys, {}, {
                                ".": this._epCompleteIfAfter
                            });
                        }
                    }
                }//else
            }
            var cnt = this.$n('cnt');
            cnt.value = this._value;
            this._editor = CodeMirror.fromTextArea(cnt, editorOptions);
            var wgt = this;
            if (this.isListen('onCursorActivity')) {
                this._editor.on("cursorActivity", function (cm) {
                    var pos = cm.getCursor();
                    if (pos.line == 0 && pos.ch == 0) return;
                    if (!cm.lastCursorPosition || pos.line != cm.lastCursorPosition.line) {
                        var c = cm.getLine(pos.line).trim().substring(0, 1);
                        var dm = cm.cursorCoords({line: pos.line, ch: pos.ch});
                        if (c == '<' && (wgt._mode == 'xml' || wgt._mode == 'eppage'))//xml
                            wgt.fire('onCursorActivity', zk.copy({
                                left: parseInt(dm.left),
                                top: parseInt(dm.top)
                            }, cm.getCursor()), {sendAhead: true});
                        else if (wgt._mode == 'epscript')//
                            wgt.fire('onCursorActivity', zk.copy({
                                left: parseInt(dm.left),
                                top: parseInt(dm.top)
                            }, cm.getCursor()), {sendAhead: true});
                    }
                    cm.lastCursorPosition = pos;
                });
            }
            if (!this._disabled) {
                this._editor.on("blur", function (cm) {
                    if (wgt._dirty) {
                        wgt._value = cm.getValue();
                        wgt.fire("onChange", {value: wgt._value});
                        wgt._dirty = false;
                    }
                    if (wgt.isListen('onBlur'))
                        wgt.fire("onBlur", {}, {sendAhead: true});
                });
                this._editor.on("focus", function (cm) {
                    if (wgt.isListen('onFocus'))
                        wgt.fire("onFocus", {}, {sendAhead: true});
                });
                this._editor.on("changes", wgt._onChanges);
                if (this._debug && this._mode == 'epscript') {
                    this._editor.on("gutterClick", function (cm, n) {
                        var info = cm.lineInfo(n);
                        if (wgt.isListen('onAddMarker') || wgt.isListen('onRemoveMarker')) {
                            if (info.gutterMarkers == null) {//增加标志
                                zAu.send(new zk.Event(wgt, "onAddMarker", {
                                    line: info.line,
                                    text: info.text
                                }, {toServer: true}));
                            } else {//删除标志
                                zAu.send(new zk.Event(wgt, "onRemoveMarker", {line: info.line}, {toServer: true}));
                            }
                        }
                        cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : wgt._makeMarker());
                    });
                }
            }
            if (this._lineWrapping) {
                var charWidth = this._editor.defaultCharWidth(), basePadding = 4;
                this._editor.on("renderLine", function (cm, line, elt) {
                    var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth;
                    elt.style.textIndent = "-" + off + "px";
                    elt.style.paddingLeft = (basePadding + off) + "px";
                });
                this._editor.refresh();
            }
            if (this._border == 'none')
                this.$n().style.border = 0;
            if (this._focus)
                this._editor.focus();
        }
        this._resize();
    },
    onSize: function () {
        this.$supers(cmez.CMeditor, 'onSize', arguments);
        this._resize();
    },
    _resize: function () {
        if (this._editor && (this._hflex || this._vflex)) {
            var n = this.$n();
            if (this._type == 'diff') {
                this._editor.editor().setSize(n.clientWidth, n.clientHeight, false);
                this._editor.rightOriginal().setSize(n.clientWidth, n.clientHeight, false);
                if (this._editor.leftOriginal())
                    this._editor.leftOriginal().setSize(n.clientWidth, n.clientHeight, false);
            } else
                this._editor.setSize(n.clientWidth, n.clientHeight, false);
        }
    },
    _onChanges: function (cm) {
        //web.log('form:ch='+vo.from.ch+',line='+vo.from.line+',to:ch='+vo.to.ch+',line='+vo.to.line+',text:'+vo.text+',removed:'+vo.removed+',origin:'+vo.origin);
        var wgt = zk.$(cm.options.uuid);
        if (wgt._smartIndent)
            cm.execCommand('indentAuto');
        wgt._value = cm.getValue();
        if (wgt.isListen('onChanging'))
            wgt.fire("onChanging", {value: wgt._value});
        else
            wgt._dirty = true;
    },
    _makeMarker: function () {
        var marker = document.createElement("div");
        marker.style.color = "#822";
        marker.innerHTML = "●";
        return marker;
    },
    _epCompleteIfAfter: function (cm) {
        var tok = cm.getTokenAt(cm.getCursor());
        if (tok.type == 'variable-2' || tok.type == 'string') {
            if (!cm.state.completionActive)
                cm.showHint({completeSingle: false});
        }
        return CodeMirror.Pass;
    },
    _xmlCompleteAfter: function (cm, pred) {
        if (!pred || pred()) setTimeout(function () {
            if (!cm.state.completionActive)
                CodeMirror.showHint(cm, CodeMirror.hint.xml, {
                    schemaInfo: tags,
                    completeSingle: false
                });
        }, 100);
        return CodeMirror.Pass;
    },
    _xmlCompleteIfAfterLt: function (cm) {
        var wgt = zk.Widget.$(cm.options.uuid);
        return wgt._xmlCompleteAfter(cm, function () {
            var cur = cm.getCursor();
            return cm.getRange(CodeMirror.Pos(cur.line, cur.ch - 1), cur) == "<";
        });
    },

    _xmlCompleteIfInTag: function (cm) {
        var wgt = zk.Widget.$(cm.options.uuid);
        return wgt._xmlCompleteAfter(cm, function () {
            var tok = cm.getTokenAt(cm.getCursor());
            if (tok.type == "string" && (!/['"]/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1)) return false;
            var inner = CodeMirror.innerMode(cm.getMode(), tok.state).state;
            return inner.tagName;
        });
    },
    domAttrs_: function (no) {
        var attr = this.$supers('domAttrs_', arguments);
        if (!this.isVisible() && (!no || !no.visible))
            attr += ' style="display:none;"';
        return attr;
    }
});